package org.rost.web.thrithon.core;

import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.Enumeration;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLFilter;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.XMLFilterImpl;
import org.xml.sax.helpers.XMLReaderFactory;

class TemplateTransformation{
    private static final String ENGINE_XML_URL = "engine";
    
    private ThrithonRequestContext ctx = null;
    private String pageTemplate = "";
    
    public TemplateTransformation(ThrithonRequestContext ctx){
        this.ctx = ctx;
        try {
            boolean isXML = ctx.getServletPath().endsWith(".xml");
            ThrithonAppContext.log("Parsing: "+ctx.getTemplate());
            pageTemplate = ctx.getTemplate();
            TransformerFactory factory = TransformerFactory.newInstance();
            Transformer transformer = factory.newTransformer();
            XMLReader reader = XMLReaderFactory.createXMLReader();
            XMLFilter filter = new EngineFilter();
            filter.setParent(reader);
            InputSource inputSource = new InputSource(ThrithonAppContext.getContext().
                        getResourceAsStream(ctx.getTemplate(), ctx));
            StreamResult result = new StreamResult(ctx.getStream());
            if(ctx.getTemplate().endsWith(".xsl") && ctx.getAttribute("actionResult")!=null){
                Source source = null;
                Object obj = ctx.getAttribute("actionResult");
                if(obj!=null){
                    if(obj instanceof Node)
                        source = new DOMSource((Element)obj);
                    else{
//                        EngineAppContext.log("XML = "+obj.toString());
                        source = new StreamSource(new StringReader(obj.toString()));
                    }
                }

                SAXSource xslSrc = new SAXSource(filter, inputSource);
                Templates template = factory.newTemplates(xslSrc);
                Transformer xsl = template.newTransformer();
                xsl.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, isXML?"no":"yes");
                applyParams(xsl, new AttributesImpl());
                xsl.transform(source, result);
            }else{
                transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, isXML?"no":"yes");
                SAXSource src = new SAXSource(filter, inputSource);
                transformer.transform(src, result);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }        
    }
    
    private void applyParams(Transformer xsl, Attributes attr){
        xsl.setParameter("contextPath", ctx.getContextPath());
        xsl.setParameter("servletPath", ctx.getServletPath());
        xsl.setParameter("actionPrefix", ThrithonAppContext.getContext().
                getActionPrefix());
        xsl.setParameter("pageTemplate", pageTemplate);
        Enumeration e = ctx.getRequest().getParameterNames();
        for(;e.hasMoreElements();){
            String pName = e.nextElement().toString();
            xsl.setParameter(pName, ctx.getRequest().getParameter(pName));
        }

        for (int i = 0; i < attr.getLength(); i++) {
            xsl.setParameter(attr.getLocalName(i), attr.getValue(i));
        }

        int i = 0;
        for (String param : ctx.getParameters()) {
            xsl.setParameter("p"+i++, param);
        }
        
    }
    
    public void applyTransform(ContentHandler handler, InputStream src, Source xmlSrc, Attributes attr){
        try {
            TransformerFactory factory = TransformerFactory.newInstance();
            
            XMLReader reader = XMLReaderFactory.createXMLReader();
            XMLFilter filter = new EngineFilter();
            filter.setParent(reader);
            SAXSource xslSrc = new SAXSource(filter, new InputSource(src));
            SAXResult sr = new SAXResult(handler);
            Templates template = factory.newTemplates(xslSrc);
            Transformer xsl = template.newTransformer();
            applyParams(xsl, attr);
            xsl.transform(xmlSrc, sr);
        } catch (Exception ex) {
            ex.printStackTrace();
            ThrithonAppContext.log(ex.toString());
        }
    }
    
    private void insertTransform(ContentHandler handler, Reader src){
        try {
            XMLReader reader = XMLReaderFactory.createXMLReader();
            EngineFilter filter = new EngineFilter();
            filter.setParent(reader);
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            SAXResult sr = new SAXResult(handler);

            SAXSource source = new SAXSource(filter, new InputSource(src));
            
            transformer.transform(source, sr);
        } catch (Exception ex) {
            ex.printStackTrace();
            ThrithonAppContext.log(ex.toString());
        }
    }
    
    private void insertTransform(ContentHandler handler, InputStream src){
        try {
            XMLReader reader = XMLReaderFactory.createXMLReader();
            EngineFilter filter = new EngineFilter();
            filter.setParent(reader);
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            SAXResult sr = new SAXResult(handler);

            SAXSource source = new SAXSource(filter, new InputSource(src));
            
            transformer.transform(source, sr);
        } catch (Exception ex) {
            ex.printStackTrace();
            ThrithonAppContext.log(ex.toString());
        }
    }
    
    class EngineFilter extends XMLFilterImpl{
        
        private int deep = 0;
        
        @Override
        public void startDocument() throws SAXException {
            if(deep==0)
                super.startDocument();
            deep++;
        }

        @Override
        public void endDocument() throws SAXException {
            deep--;
            if(deep==0)
                super.endDocument();
        }
        
        
        @Override
        public void startPrefixMapping(String prefix, String uri) throws SAXException {
//            EngineAppContext.log("startPrefixMapping prefix = "+prefix+", uri = "+uri);
//            if(!ENGINE_XML_URL.equals(uri))
                super.startPrefixMapping(prefix, uri);
        }

        @Override
        public void endPrefixMapping(String prefix) throws SAXException {
//            EngineAppContext.log("endPrefixMapping prefix = "+prefix);
            super.endPrefixMapping(prefix);
        }
        
        @Override
        public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
            AttributesImpl att = new AttributesImpl(atts);
//            EngineAppContext.log("startElement: qName = "+qName+", lName = "+localName+", uri = "+uri);
            if(ENGINE_XML_URL.equals(uri)){
                if("include".equals(localName)){
                    if(atts.getValue("stringsource")!=null){
                        ctx.setSaxAttributes(att);
                        Object obj = ctx.callBean(atts.getValue("stringsource"), 
                            atts.getValue("scope"));
                        ctx.setSaxAttributes(null);
                        insertTransform(this, new StringReader(obj.toString()));
                    }
                    if(atts.getValue("filesource")!=null || atts.getValue("src")!=null){
                        String src = atts.getValue("filesource")!=null?atts.getValue("filesource"):atts.getValue("src");
                        InputStream source = ThrithonAppContext.getContext().getResourceAsStream(src, ctx);
                        insertTransform(this, source);
                    }
                }
                if("apply".equals(localName)){
                    String template = atts.getValue("template");
                    Source source = null;
                    if(atts.getValue("domsource")!=null || atts.getValue("stringsource")!=null || atts.getValue("src")!=null){
                        ctx.setSaxAttributes(att);
                        String src = atts.getValue("domsource")!=null?
                            atts.getValue("domsource"):atts.getValue("stringsource");
                        if(src==null)
                            src = atts.getValue("src");
                        Object obj = ctx.callBean(src, 
                            atts.getValue("scope"));
                        ctx.setSaxAttributes(null);
                        if(obj!=null){
                            if(obj instanceof Node)
                                source = new DOMSource((Element)obj);
                            else{
                                source = new StreamSource(new StringReader(obj.toString()));
                            }
                        }
                    }
                    if(atts.getValue("filesource")!=null){
                        source = new StreamSource(ThrithonAppContext.getContext().
                            getResourceAsStream(atts.getValue("filesource"), ctx));
                    }
                    if(source==null){
                        //Create empty source
                        source = new StreamSource(new StringReader("<root></root>"));
                    }
                    if(source!=null){
                        applyTransform(this, ThrithonAppContext.getContext().
                            getResourceAsStream(template, ctx), source, att);
                    }
                }
                if(("a".equals(localName) || "link".equals(localName)) && att.getValue("href")!=null){
                    
                    att.setValue(att.getIndex("href"), ctx.getHTMLPath(att.getValue("href")));
                    super.startElement("", localName, localName, att);
                }
                if(("img".equals(localName) || "script".equals(localName)) && att.getValue("src")!=null){
                    att.setValue(att.getIndex("src"), ctx.getHTMLPath(att.getValue("src")));
                    super.startElement("", localName, localName, att);
                }
                if("form".equals(localName)){
                    String action = att.getValue("action");
                    if(action==null)
                        action = "";
                    if(att.getValue("template")!=null){
                        action = ThrithonAppContext.getContext().getActionPrefix()+"/"+action+att.getValue("template");
                        att.removeAttribute(att.getIndex("template"));
                    }
                    action = ctx.getContextPath()+action;
                    att.setValue(att.getIndex("action"), action);
                    super.startElement("", localName, localName, att);
                }
                return;
            }
            super.startElement(uri, localName, qName, att);
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            if(ENGINE_XML_URL.equals(uri)){
                if("a".equals(localName) || "img".equals(localName) || 
                        "form".equals(localName) || "script".equals(localName) || 
                        "link".equals(localName)){
                    super.endElement("", localName, localName);
                }
                return;
            }
            super.endElement(uri, localName, qName);
        }
    }
    
}

/**
 *
 * @author Kostya
 */
public class TemplateFilter implements ThrithonFilter{
        
    public void processing(ThrithonRequestContext ctx) throws ThrithonException {
        if(!"".equals(ctx.getTemplate()))
            new TemplateTransformation(ctx);
    }

    public void postprocessing(ThrithonRequestContext ctx) throws ThrithonException {
    }
}
